#include <v8.h>
#include <dssi.h>
#include <ladspa.h>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <string.h>

#include <math.h>
#include <stdio.h>

static LADSPA_Descriptor *jstLDescriptor = NULL;
static DSSI_Descriptor *jstDDescriptor = NULL;
bool ExecuteString(
	v8::Handle<v8::String> source,
	v8::Handle<v8::Value> name,
	bool print_result,
	bool report_exceptions
);
bool ExecuteChar(char * src);
v8::Handle<v8::Value> glEcho(const v8::Arguments& args);
v8::Handle<v8::Value> glReadfile(const v8::Arguments& args);
v8::Handle<v8::Value> glImport(const v8::Arguments& args);
v8::Handle<v8::String> ReadFile(const char* name);
void ReportException(v8::TryCatch* handler);

#define JST_OUTPUT 0

static void run_JST(LADSPA_Handle instance, unsigned long sample_count,
		snd_seq_event_t * events, unsigned long EventCount);
int startJST();

typedef struct {
	LADSPA_Data *output;
} JST;

const LADSPA_Descriptor *ladspa_descriptor(unsigned long index)
{
	switch (index) {
	case 0:
	return jstLDescriptor;
	default:
	return NULL;
	}
}

const DSSI_Descriptor *dssi_descriptor(unsigned long index)
{
	switch (index) {
	case 0:
	return jstDDescriptor;
	default:
	return NULL;
	}
}

/*int main(int argc, char* argv[])
{
	int result = startJST(argc, argv);
	v8::V8::Dispose();
	return result;
}
*/
//int main(){}

static void cleanupJST(LADSPA_Handle instance)
{
	free(instance);
}

static void connectPortJST(LADSPA_Handle instance, unsigned long port,
			  LADSPA_Data * data)
{
	JST *plugin;

	plugin = (JST *) instance;
	switch (port)
	{
		case JST_OUTPUT:
		plugin->output = data;
		break;
	}
}

static LADSPA_Handle instantiateJST(const LADSPA_Descriptor * descriptor,
				   unsigned long s_rate)
{
	unsigned int i;

	JST *plugin_data = (JST *) malloc(sizeof(JST));

	return (LADSPA_Handle) plugin_data;
}

static void activateJST(LADSPA_Handle instance)
{
	startJST();
	JST *plugin_data = (JST *) instance;
	unsigned int i;
}

static void run_JSTWrapper(LADSPA_Handle instance,
			 unsigned long sample_count)
{
	run_JST(instance, sample_count, NULL, 0);
}

static void run_JST(LADSPA_Handle instance, unsigned long sample_count,
		  snd_seq_event_t *events, unsigned long event_count)
{
	JST *plugin_data = (JST *) instance;
	LADSPA_Data *const output = plugin_data->output;
	unsigned long pos;
	unsigned long event_pos;
	unsigned long note;

	if (event_count > 0)
	{
		printf("DSSI-JST: encountered %ld events\n", event_count);
	}

	for (pos = 0, event_pos = 0; pos < sample_count; pos++)
	{

		while (event_pos < event_count && pos == events[event_pos].time.tick)
		{

			printf("DSSI-JST: event type %d\n", events[event_pos].type);

			if (events[event_pos].type == SND_SEQ_EVENT_NOTEON)
			{
				char * buf;
				sprintf(
					buf,
					"onmidireceive('noteon', %d, %d);",
					events[event_pos].data.note.note,
					events[event_pos].data.note.velocity
				);
				ExecuteChar(buf);
			}
			else if (events[event_pos].type == SND_SEQ_EVENT_NOTEOFF)
			{
				char * buf;
				sprintf(
					buf,
					"onmidireceive('noteoff', %d, %d);",
					events[event_pos].data.note.note,
					events[event_pos].data.note.velocity
				);
				ExecuteChar(buf);
			}
			event_pos++;
		}

		output[pos] = 0.0f;
	}
}

int getControllerJST(LADSPA_Handle instance, unsigned long port)
{
	/*switch (port) {
	case JST_VOLUME:
	return DSSI_CC(7);
	case JST_FREQ:
	return DSSI_CC(9);
	}*/

	return -1;
}

#ifdef __GNUC__
__attribute__((constructor)) void init()
#else
void _init()
#endif
{
	char **port_names;
	LADSPA_PortDescriptor *port_descriptors;
	LADSPA_PortRangeHint *port_range_hints;

	jstLDescriptor = (LADSPA_Descriptor *) malloc(sizeof(LADSPA_Descriptor));
	if (jstLDescriptor)
	{
		jstLDescriptor->UniqueID = 24;
		jstLDescriptor->Label = "JST";
		jstLDescriptor->Properties = 0;
		jstLDescriptor->Name = "DSSI-JST";
		jstLDescriptor->Maker = "Jussi Kalliokoski <jussi.kalliokoski@gmail.com>";
		jstLDescriptor->Copyright = "Public Domain";
		jstLDescriptor->PortCount = 1;

		port_descriptors = (LADSPA_PortDescriptor *)
					calloc(jstLDescriptor->PortCount, sizeof
							(LADSPA_PortDescriptor));
		jstLDescriptor->PortDescriptors =
				(const LADSPA_PortDescriptor *) port_descriptors;

		port_range_hints = (LADSPA_PortRangeHint *)
					calloc(jstLDescriptor->PortCount, sizeof
							(LADSPA_PortRangeHint));
		jstLDescriptor->PortRangeHints =
				(const LADSPA_PortRangeHint *) port_range_hints;

		port_names = (char **) calloc(jstLDescriptor->PortCount, sizeof(char *));
		jstLDescriptor->PortNames = (const char **) port_names;

		port_descriptors[JST_OUTPUT] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
		port_names[JST_OUTPUT] = "Output";
		port_range_hints[JST_OUTPUT].HintDescriptor = 0;

		jstLDescriptor->activate = activateJST;
		jstLDescriptor->cleanup = cleanupJST;
		jstLDescriptor->connect_port = connectPortJST;
		jstLDescriptor->deactivate = NULL;
		jstLDescriptor->instantiate = instantiateJST;
		jstLDescriptor->run = run_JSTWrapper;
		jstLDescriptor->run_adding = NULL;
		jstLDescriptor->set_run_adding_gain = NULL;
	}

	jstDDescriptor = (DSSI_Descriptor *) malloc(sizeof(DSSI_Descriptor));
	if (jstDDescriptor)
	{
		jstDDescriptor->DSSI_API_Version = 1;
		jstDDescriptor->LADSPA_Plugin = jstLDescriptor;
		jstDDescriptor->configure = NULL;
		jstDDescriptor->get_program = NULL;
		jstDDescriptor->get_midi_controller_for_port = getControllerJST;
		jstDDescriptor->select_program = NULL;
		jstDDescriptor->run_synth = run_JST;
		jstDDescriptor->run_synth_adding = NULL;
		jstDDescriptor->run_multiple_synths = NULL;
		jstDDescriptor->run_multiple_synths_adding = NULL;
	}
}

#ifdef __GNUC__
__attribute__((destructor)) void fini()
#else
void _fini()
#endif
{
	if (jstLDescriptor)
	{
		free((LADSPA_PortDescriptor *) jstLDescriptor->PortDescriptors);
		free((char **) jstLDescriptor->PortNames);
		free((LADSPA_PortRangeHint *) jstLDescriptor->PortRangeHints);
		free(jstLDescriptor);
	}
	if (jstDDescriptor)
	{
		free(jstDDescriptor);
	}
	v8::V8::Dispose();
}



int startJST(/*int argc, char* argv[]*/)
{
	/*if (argc != 2)
	{
		printf("Usage: %s filepath\n", argv[0]);
		return 0;
	}*/

	v8::HandleScope handle_scope;

	v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
	global->Set(v8::String::New("echo"), v8::FunctionTemplate::New(glEcho));
	global->Set(v8::String::New("readfile"), v8::FunctionTemplate::New(glReadfile));
	global->Set(v8::String::New("import"), v8::FunctionTemplate::New(glImport));

	v8::Handle<v8::Context> context = v8::Context::New(NULL, global);
	v8::Context::Scope context_scope(context);

	char * str = "jst.js";// = "jst.js";//argv[1];

	v8::Handle<v8::String> file_name = v8::String::New(str);
	v8::Handle<v8::String> source = ReadFile(str);
	if (source.IsEmpty())
	{
		printf("Error reading '%s'\n", str);
		return 1;
	}
	if (!ExecuteString(source, file_name, false, true))
		return 1;

	return 0;
}

const char* ToCString(const v8::String::Utf8Value& value) {
	return *value ? *value : "<string conversion failed>";
}

v8::Handle<v8::Value> glEcho(const v8::Arguments& args) {
	bool first = true;
	for (int i = 0; i < args.Length(); i++) {
		v8::HandleScope handle_scope;
		if (first) {
			first = false;
		} else {
			printf(" ");
		}
		v8::String::Utf8Value str(args[i]);
		const char* cstr = ToCString(str);
		printf("%s", cstr);
	}
	printf("\n");
	fflush(stdout);
	return v8::Undefined();
}

v8::Handle<v8::Value> glReadfile(const v8::Arguments& args) {
	if (args.Length() != 1) {
		return v8::ThrowException(v8::String::New("Bad parameters"));
	}
	v8::String::Utf8Value file(args[0]);
	if (*file == NULL) {
		return v8::ThrowException(v8::String::New("Error loading file"));
	}
	v8::Handle<v8::String> source = ReadFile(*file);
	if (source.IsEmpty()) {
		return v8::ThrowException(v8::String::New("Error loading file"));
	}
	return source;
}

v8::Handle<v8::Value> glImport(const v8::Arguments& args) {
	for (int i = 0; i < args.Length(); i++) {
		v8::HandleScope handle_scope;
		v8::String::Utf8Value file(args[i]);
		if (*file == NULL) {
			return v8::ThrowException(v8::String::New("Error loading file"));
		}
		v8::Handle<v8::String> source = ReadFile(*file);
		if (source.IsEmpty()) {
			return v8::ThrowException(v8::String::New("Error loading file"));
		}
		if (!ExecuteString(source, v8::String::New(*file), false, false)) {
			return v8::ThrowException(v8::String::New("Error executing file"));
		}
	}
	return v8::Undefined();
}

v8::Handle<v8::String> ReadFile(const char* name) {
	FILE* file = fopen(name, "rb");
	if (file == NULL) return v8::Handle<v8::String>();

	fseek(file, 0, SEEK_END);
	int size = ftell(file);
	rewind(file);

	char* chars = new char[size + 1];
	chars[size] = '\0';
	for (int i = 0; i < size;) {
		int read = fread(&chars[i], 1, size - i, file);
		i += read;
	}
	fclose(file);
	v8::Handle<v8::String> result = v8::String::New(chars, size);
	delete[] chars;
	return result;
}

bool ExecuteChar(char * src)
{
	v8::Handle<v8::String> source = v8::String::New(src);
	v8::Handle<v8::String> name = v8::String::New("untitled");
	return ExecuteString(source, name, false, false);
}

bool ExecuteString(
	v8::Handle<v8::String> source,
	v8::Handle<v8::Value> name,
	bool print_result,
	bool report_exceptions
){
	v8::HandleScope handle_scope;
	v8::TryCatch try_catch;
	v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
	if (script.IsEmpty())
	{
		if (report_exceptions)
			ReportException(&try_catch);
		return false;
	}
	else
	{
		v8::Handle<v8::Value> result = script->Run();
		if (result.IsEmpty()) {
			if (report_exceptions)
				ReportException(&try_catch);
			return false;
		}
		else
		{
			if (print_result && !result->IsUndefined())
			{
				v8::String::Utf8Value str(result);
				const char* cstr = ToCString(str);
				printf("%s\n", cstr);
			}
			return true;
		}
	}
}

	
void ReportException(v8::TryCatch* try_catch)
{
	v8::HandleScope handle_scope;
	v8::String::Utf8Value exception(try_catch->Exception());
	const char* exception_string = ToCString(exception);
	v8::Handle<v8::Message> message = try_catch->Message();
	if (message.IsEmpty())
	{
		printf("%s\n", exception_string);
	}
	else
	{
		v8::String::Utf8Value filename(message->GetScriptResourceName());
		const char* filename_string = ToCString(filename);
		int linenum = message->GetLineNumber();
		printf("%s:%i: %s\n", filename_string, linenum, exception_string);
		v8::String::Utf8Value sourceline(message->GetSourceLine());
		const char* sourceline_string = ToCString(sourceline);
		printf("%s\n", sourceline_string);
		int start = message->GetStartColumn();
		for (int i = 0; i < start; i++)
		{
			printf(" ");
		}
		int end = message->GetEndColumn();
		for (int i = start; i < end; i++)
		{
			printf("^");
		}
		printf("\n");
		v8::String::Utf8Value stack_trace(try_catch->StackTrace());
		if (stack_trace.length() > 0)
		{
			const char* stack_trace_string = ToCString(stack_trace);
			printf("%s\n", stack_trace_string);
		}
	}
}
 
